/**
 * Copyright (c) 2022 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "plugins/ecmascript/tests/runtime/common/test_helper.h"

#include <cstddef>

#include "plugins/ecmascript/runtime/ecma_vm.h"
#include "plugins/ecmascript/runtime/js_thread.h"
#include "plugins/ecmascript/runtime/napi/include/jsnapi.h"
#include "plugins/ecmascript/runtime/napi/jsnapi_helper-inl.h"
#include "plugins/ecmascript/runtime/object_factory.h"
#include "plugins/ecmascript/runtime/js_weak_container.h"
#include "plugins/ecmascript/runtime/js_tagged_value-inl.h"

#include "libpandabase/os/thread.h"

// NOLINTNEXTLINE(google-build-using-namespace)
using namespace ark::ecmascript;

namespace ark::test {
class WeakContainersTests : public testing::Test {
public:
    void SetUp() override
    {
        RuntimeOption option;
        option.SetGcType(RuntimeOption::GcType::G1_GC);
        option.SetLogLevel(RuntimeOption::LogLevel::ERROR);
        option.SetExplicitConcurrentGcEnabled(false);
        vm_ = JSNApi::CreateJSVM(option);
        ASSERT_TRUE(vm_ != nullptr) << "Cannot create Runtime";
        thread_ = vm_->GetAssociatedJSThread();
        vm_->GetFactory()->SetTriggerGc(true);
    }

    void TearDown() override
    {
        vm_->GetFactory()->SetTriggerGc(false);
        JSNApi::DestroyJSVM(vm_);
    }

protected:
    // NOLINTNEXTLINE(misc-non-private-member-variables-in-classes)
    EcmaVM *vm_ = nullptr;

private:
    JSThread *thread_ = nullptr;
};

Local<JSValueRef> RunGC(EcmaVM *vm /*unused*/, Local<JSValueRef> /*unused*/, const Local<JSValueRef> * /*unused*/,
                        int32_t /*unused*/, void * /*unused*/)
{
    vm->GetGC()->WaitForGC(GCTask(GCTaskCause::EXPLICIT_CAUSE));
    return JSValueRef::Undefined(vm);
}

Local<JSValueRef> GetContainerSize(EcmaVM *vm, Local<JSValueRef> /*unused*/, const Local<JSValueRef> *args,
                                   int32_t argc, void * /*unused*/)
{
    if (argc != 1) {
        LOG_ECMA(FATAL) << "Unexpected number of arguments: " << argc;
    }
    // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
    JSHandle<JSTaggedValue> value = JSNApiHelper::ToJSHandle(args[0]);
    if (!value->IsHeapObject()) {
        LOG_ECMA(FATAL) << "Argument must be heap object";
    }
    auto hclass = value->GetTaggedObject()->GetClass();
    if (!hclass->IsJSWeakMap() && !hclass->IsJSWeakSet()) {
        LOG_ECMA(FATAL) << "Argument is not weak container";
    }
    if (hclass->IsJSWeakMap()) {
        auto *weakMap = JSWeakMap::Cast(value->GetHeapObject());
        return IntegerRef::New(vm, weakMap->GetSize());
    }

    if (hclass->IsJSWeakSet()) {
        auto *weakSet = JSWeakSet::Cast(value->GetHeapObject());
        return IntegerRef::New(vm, weakSet->GetSize());
    }
    UNREACHABLE();
}

bool RegisterFunction(EcmaVM *vm, Local<ObjectRef> &globalObject, ark::FunctionCallback callback, const char *name)
{
    Local<FunctionRef> functionRef = FunctionRef::New(vm, callback, nullptr);
    if (functionRef.IsEmpty()) {
        return false;
    }
    Local<ObjectRef> key = StringRef::NewFromUtf8(vm, name);
    bool result = globalObject->Set(vm, key, functionRef);
    return result;
}

TEST_F(WeakContainersTests, WeakContainers)
{
    // NOLINTNEXTLINE(modernize-avoid-c-arrays)
    const char mainFunc[] = "_GLOBAL::func_main_0";
    JSExecutionScope executionScope(vm_);
    LocalScope scope(vm_);

    Local<ObjectRef> globalObject = JSNApi::GetGlobalObject(vm_);

    ASSERT_TRUE(RegisterFunction(vm_, globalObject, RunGC, "collectGarbage"));
    ASSERT_TRUE(RegisterFunction(vm_, globalObject, GetContainerSize, "getContainerSize"));

    // NOLINTNEXTLINE(modernize-avoid-c-arrays)
    const char fileName[] = "weakContainers.abc";
    bool ret2 = JSNApi::Execute(vm_, fileName, mainFunc);
    ASSERT_EQ(ret2, true);
    Local<ObjectRef> exception = JSNApi::GetUncaughtException(vm_);
    if (!exception.IsEmpty() && !exception->IsHole()) {
        Local<ObjectRef> msgKey = StringRef::NewFromUtf8(vm_, "message");
        auto msg = exception->Get(vm_, msgKey)->ToString(vm_);
        FAIL() << msg->ToString();
    }
}
}  // namespace ark::test
